#include "client.h"
#include <string.h>
#include <stdio.h>
#include <ncurses.h>
#include <unistd.h>

#ifdef KEY_ENTER
#undef KEY_ENTER
#define KEY_ENTER	10
#endif

// porneste monitorul tastaturii
void *startKeyboardMonitor(void *threadarg) {
	((Client*)threadarg)->monitorKeyboard();
	pthread_exit(NULL);
}

// porneste monitorul socketului
void *startSocketMonitor(void *threadarg) {
	((Client*)threadarg)->monitorSocket();
	pthread_exit(NULL);
}

void Client::draw_get_msg(char *prompt, int last_row, char *partial_msg) {
	// curata promptul
	mvprintw(last_row-1,0,"                                                                           ",prompt);
	// afiseaza promptul + mesajul
	if(partial_msg) {
		mvprintw(last_row-1,0,"%s",prompt);
		mvprintw(last_row-1,strlen(prompt),"%s",partial_msg);
	} else {
		mvprintw(last_row-1,0,"%s",prompt);
	}
	refresh();
}

void Client::get_msg(char *prompt, int last_row, char *msg) {
	// ch ultimul caracter citit
	// len lungimea mesajului curent
	int ch, len=0;
	msg[0] = '\0';
	
	// deseneazaa promptul gol
	draw_get_msg(prompt, last_row, msg);
	
	while(work) {
		// testeaza asincron daca s-a apasat o tasta
		if ((ch = getch()) == ERR) {
			// daca nu s-a apasat, deseneaza promptul si stai 100 ms
			draw_get_msg(prompt, last_row, msg);
			usleep(100);
			draw_get_msg(prompt, last_row, msg);
		} else {
			if(ch == KEY_BACKSPACE) {
				// s-a apasat backspace
				if(len > 0) {
					msg[--len] = '\0';
				}
			} else if(ch == KEY_ENTER) {
				// s-a apasat enter
				draw_get_msg(prompt, last_row, NULL);
				return;
			} else {
				// s-a introdus alt caracter
				msg[len] = ch;
				msg[++len] = '\0';
				draw_get_msg(prompt, last_row, msg);
			}
		}
	}
}

void Client::print_msg(int x, int y, char *msg)
{
	mvprintw(x,y,"%s",msg);
}

void Client::start()
{
	// initializeaza socket
	if(m_socket.callSocket(m_hostname, m_portNumber) < 0) {
		printf("Client::start::ERROR: Cannot connect to the server.\n");
		return;
	}
	
	// initializeaza consola ncurses
	initscr();
	
	// fara delay in transmiterea caracterelor
	nodelay(stdscr, TRUE);
	
	// prinde taste speciale
	keypad(stdscr, TRUE);
	
	// nu afisa ce se tasteaza
	noecho();
	
	// fara buffer
	cbreak();
	
	// vedem cat e de mare consola
	getmaxyx(stdscr, row_max, col_max);
	
	// ma asigur ca threadurile is joinable
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
	
	// creeaza threadul keyboard monitor
	if (pthread_create(&keyboard_monitor, &attr, startKeyboardMonitor, (void *)this))
	{
		printf("Client::start::ERROR: la pthread_create()\n");
		return;
	}
	
	// creeaza threadul socket monitor
	if (pthread_create(&socket_monitor, &attr, startSocketMonitor, (void *)this))
	{
		printf("Client::start::ERROR: la pthread_create()\n");
		return;
	}
	
	// asteapta terminarea threadurilor
	pthread_join(keyboard_monitor,NULL);
	pthread_join(socket_monitor,NULL);
}

void Client::stop()
{
	// trimite un logout message
	Socket::sendMessage(this->m_socket.getSocket(), NC_LOGOUT, NULL);
	
	pthread_mutex_lock(&mutex);
	// opreste rularea threadurilor
	work = 0;
	pthread_mutex_unlock(&mutex);
}

const char *Client::md5(const char *str) {
	m_md5.setPlainText(str);
	return m_md5.getMD5Digest();
}

void Client::monitorKeyboard() {
	int serverSocket = this->m_socket.getSocket();
	while(work) {
		// citeste mesaj de la tastatura asincron
		get_msg(prompt,row_max,msg);
		
		// am primit comanda exit
		if(!strcmp(msg,"exit") || !strcmp(msg,"quit")) {
			// trimite un logout message
			Socket::sendMessage(this->m_socket.getSocket(), NC_LOGOUT, NULL);
				
			pthread_mutex_lock(&mutex);
			// opreste rularea threadurilor
			work = 0;
			pthread_mutex_unlock(&mutex);
			
			return;
		}
		
		Socket::sendMessage(serverSocket, NC_MESSAGE, msg);
	}
}

void Client::monitorSocket() {
	int serverSocket = this->m_socket.getSocket();
	int type, auth_ok = 0;
	char current_message[256];

	while(work) {
		
		// citeste urmatorul mesaj de la server
		Socket::getMessage(serverSocket, type, current_message);

		switch(type) {
			case NC_USERNAME_REQUEST: // username request
				// trimite un username response
				Socket::sendMessage(serverSocket, NC_USERNAME_RESPONSE, m_username);
				break;

			case NC_PASSWORD_REQUEST: // password request
				// trimite un password response
				Socket::sendMessage(serverSocket, NC_PASSWORD_RESPONSE, m_password);
				break;

			case NC_AUTH_OK : // authorization OK
				auth_ok = 1;
				break;

			case NC_AUTH_FAILED : // authorization failed
				// trimite un logout message
				Socket::sendMessage(this->m_socket.getSocket(), NC_LOGOUT, NULL);
				pthread_mutex_lock(&mutex);
				work = 0;
				pthread_mutex_unlock(&mutex);
				auth_failed = true;
				auth_ok = 0;
				return;
				break;

			case NC_BROADCAST : // broadcast received
				print_msg(x,0,current_message);
				//inctementam x-ul, ca sa scriem urmatorul mesaj pe randul urmator
				x++;
				break;
				
			case NC_SHUTDOWN: // shutdown message
				// confirma printr-un logout message
				Socket::sendMessage(this->m_socket.getSocket(), NC_LOGOUT, NULL);
							
				pthread_mutex_lock(&mutex);
				// opreste rularea threadurilor
				work = 0;
				pthread_mutex_unlock(&mutex);
				return;
				break;
		}
	}
}

Client::Client(const char *hostname, int portNumber, const char *username, const char *password) {
	m_portNumber = portNumber;
	m_hostname = new char[strlen(hostname)+1];
	strcpy(m_hostname, hostname);
	m_username = new char[strlen(username)+1];
	strcpy(m_username, username);
	
	m_password = new char[strlen(md5(password))+1];
	strcpy(m_password, md5(password));
	prompt = strdup("> ");
	x=0;
	
	work = 1;
	
	//initializare concurenta
	pthread_mutex_init(&mutex, NULL);
	
	auth_failed = false;
}

Client::~Client() {
	if(m_hostname) {
		delete []m_hostname;
		m_hostname = 0;
	}
	if(m_username) {
		delete []m_username;
		m_username = 0;
	}
	if(m_password) {
		delete []m_password;
		m_password = 0;
	}
	
	endwin();
	
	if(auth_failed) {
		printf("Authorization failed. Please try again.\n");
	}
}
